/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

#include <math.h>

#include "common.h"
#include "HQAAPolygonRenderer.h"
#include "HQAALookupTable.h"

#include "util.h"


using namespace Scenic::Renderer;


static ResourcePool filterKernelPool;


FilterKernel::FilterKernel(double (*func)(double x, double y, void *data), void *data)
: Resource(&filterKernelPool)
{
	size = 32;
	depth = 32;
	lookupTable = new double[size * size * depth];

	memset(lookupTable, 0, size * size * depth * sizeof(double));

	printf("%lf\n", func(-0.5, 0, data));
	printf("%lf\n", func(0, 0, data));
	printf("%lf\n", func(0.5, 0, data));
	calcLookupTable(func, data, lookupTable, size, depth);

	texture = NULL;
}

FilterKernel::~FilterKernel()
{
	delete lookupTable;
	if(texture)
		texture->release();
}

FilterKernel *FilterKernel::get(int id)
{
	return (FilterKernel*)filterKernelPool.get(id);
}

void FilterKernel::createTexture()
{
	if(texture && !texture->contentsLost())
		return;

	printf("create texture\n");

	if(texture)
		texture->release();

	texture = new VolumeTexture(size, size, depth, FMT_ABGR_16F);
	texture->write(lookupTable, FMT_AL_64F);
}

HQAAPolygonRenderer::HQAAPolygonRenderer(Context *context)
{
	this->context = context;
	kernel = context->filterKernel;
	buffer = new VertexStore(0, context, 0);
	vs = new VertexStore(0, context, 0);
	subVs = new VertexStore(VSF_TEX1 | VSF_TEXCOORD3, context, 0);
	addVs = new VertexStore(VSF_TEX1 | VSF_TEXCOORD3, context, 0);
	corner = 0;

	filterSize.x = context->filterWidth / 2.0;
	filterSize.y = context->filterHeight / 2.0;

	color = 0x40404040;

	kernel->createTexture();
}

HQAAPolygonRenderer::~HQAAPolygonRenderer()
{
	delete buffer;
	delete vs;
	delete subVs;
	delete addVs;
}

void HQAAPolygonRenderer::begin()
{
	context->setPrimitives(0);

}

void HQAAPolygonRenderer::end()
{
	corner = 0;
	draw();
	context->device->setTexture(0, NULL);
}

void HQAAPolygonRenderer::addPoint(double x, double y)
{
	buffer->addVertex(x, y, color, 1.0, 1.0);
}

void HQAAPolygonRenderer::addTriangle(int vertex1, bool edge1, int vertex2, bool edge2, int vertex3, bool edge3)
{
	TessVertex v1;
	TessVertex v2;
	TessVertex v3;

	v1.p[0] = buffer->getVertex(vertex1).p[0];
	v1.p[1] = buffer->getVertex(vertex1).p[1];

	v2.p[0] = buffer->getVertex(vertex2).p[0];
	v2.p[1] = buffer->getVertex(vertex2).p[1];

	v3.p[0] = buffer->getVertex(vertex3).p[0];
	v3.p[1] = buffer->getVertex(vertex3).p[1];

	triangle[0] = &v1;
	triangle[1] = &v2;
	triangle[2] = &v3;
	triangleEdge[0] = edge1;
	triangleEdge[1] = edge2;
	triangleEdge[2] = edge3;

	processTriangle();
}

void HQAAPolygonRenderer::draw()
{
	int color = 0x40404040;

	context->device->setBlendFunc(BLEND_ONE, BLEND_ONE);

	context->device->setTexture(0, NULL);
	vs->draw();
	context->device->setTexture(0, kernel->texture);
	context->device->setTextureStageModulation(0, 1);
	context->device->setSamplerStates(0, FILTER_LINEAR, ADDRESS_CLAMP);
/*	addVs->empty();
	addVs->addVertex(-50.0, -50.0, color, 0.0, 0.0);
	addVs->addVertex(500.0, -50.0, color, 1.0, 0.0);
	addVs->addVertex(-50.0, 500.0, color, 0.0, 1.0);
	addVs->addVertex(500.0, 500.0, color, 1.0, 1.0);
	addVs->addTriangle(0, 1, 2);
	addVs->addTriangle(3, 1, 2);
	addVs->endBlock();  */
	addVs->draw(); 

	context->device->setState(STATE_BLENDOP, BLENDOP_REVSUBTRACT);
	context->device->setState(STATE_BLENDOPALPHA, BLENDOP_REVSUBTRACT);
	subVs->draw();

	context->device->setState(STATE_BLENDOP, BLENDOP_ADD);
	context->device->setState(STATE_BLENDOPALPHA, BLENDOP_ADD);
	context->device->setTexture(0, NULL);
}

void HQAAPolygonRenderer::processTriangle()
{
	int i;

	for(i = 0; i < 3; i++)
	{
		TessVertex *data = triangle[i];

		vs->addVertex(data->p[0], data->p[1], color);
	}
	vs->addTriangle(0, 1, 2);
	vs->endBlock();

	for(i = 0; i < 3; i++)
	{
		if(triangleEdge[i])
		{
			TessVertex *v1 = triangle[i];
			TessVertex *v2 = triangle[(i + 1) % 3];
	
			drawEdge(v1, v2);
		}
	} 
}

static double fastAngle(double x, double y)
{
	if(x > y)
		return y / x;
	else
		return 2.0 - x / y;
}

static double sign(double x)
{
	return (x >= 0.0) ? 1 : -1;
}

static void copyTexCoord(double *p1, double *p2)
{
	p1[0] = p2[0];
	p1[1] = p2[1];
	p1[2] = p2[2];
}

void HQAAPolygonRenderer::setLookupTableCoordinates(int x, int y, double z, double *p)
{
	p[0] = (x + 0.5) / kernel->texture->getWidth();
	p[1] = (y + 0.5) / kernel->texture->getHeight();
	p[2] = z;
}

void HQAAPolygonRenderer::drawCorner(Double2 p, double tableZ, double mx, double my, double diffuse)
{
	VERTEX_INFO v1, v2, v3, v4, v5;
	int w = kernel->texture->getWidth();
	VertexStore *vertices = diffuse > 0.0 ? addVs : subVs;

	v1.p[0] = p.x + mx;
	v1.p[1] = p.y + my;
	setLookupTableCoordinates(0, w - 4, tableZ, v1.tex[0]);
	v1.diffuse = color;

	v2.p[0] = p.x + mx;
	v2.p[1] = p.y - my;
	setLookupTableCoordinates(0, 0, tableZ, v2.tex[0]);
	v2.diffuse = color;

	v3.p[0] = p.x - mx;
	v3.p[1] = p.y - my;
	setLookupTableCoordinates(w - 4, 0, tableZ, v3.tex[0]);
	v3.diffuse = color;

	v4.p[0] = p.x;
	v4.p[1] = p.y;
	v4.tex[0][0] = (v1.tex[0][0] + v3.tex[0][0]) / 2.0;
	v4.tex[0][1] = (v1.tex[0][1] + v3.tex[0][1]) / 2.0;
	v4.tex[0][2] = tableZ;
	v4.diffuse = color;

	v5.p[0] = p.x;
	v5.p[1] = p.y - my;
	v5.tex[0][0] = (v2.tex[0][0] + v3.tex[0][0]) / 2.0;
	v5.tex[0][1] = (v2.tex[0][1] + v3.tex[0][1]) / 2.0;
	v5.tex[0][2] = tableZ;
	v5.diffuse = color;

	int index1 = vertices->addVertex(&v1);
	int index2 = vertices->addVertex(&v2);
	int index3 = vertices->addVertex(&v3);
	int index4 = vertices->addVertex(&v4);
	int index5 = vertices->addVertex(&v5);

	vertices->addTriangle(index1, index2, index4);
	vertices->addTriangle(index2, index5, index4);

	v3.tex[0][0] = 1.0 - v3.tex[0][0];
	v3.tex[0][1] = 1.0 - v3.tex[0][1];

	v4.tex[0][0] = 1.0 - v4.tex[0][0];
	v4.tex[0][1] = 1.0 - v4.tex[0][1];

	v5.tex[0][0] = 1.0 - v5.tex[0][0];
	v5.tex[0][1] = 1.0 - v5.tex[0][1];

	index3 = vertices->addVertex(&v3);
	index4 = vertices->addVertex(&v4);
	index5 = vertices->addVertex(&v5);

	vertices->addTriangle(index5, index3, index4);
	vertices->endBlock();
}

void HQAAPolygonRenderer::drawEdge(TessVertex *vertex2, TessVertex *vertex1)
{
	Double2 p1 = {vertex1->p[0], vertex1->p[1]};
	Double2 p2 = {vertex2->p[0], vertex2->p[1]};
	Double2 d;
	double angle;
	double mx, my;
	VERTEX_INFO v1, v2, v3, v4;
	int w = kernel->texture->getWidth();
	int h = kernel->texture->getHeight();
	int depth = kernel->texture->getDepth();
	double a = 1.0 / w / 2.0;
	double diffuse;
	double tableZ;
	VertexStore *vertices;

	d.x = p2.x - p1.x;
	d.y = p2.y - p1.y;

	angle = fastAngle(fabs(d.x), fabs(d.y));
	tableZ = (0.5 + (angle / 2.0) * (depth - 1)) / depth;

	mx = sign(d.y);
	my = -sign(d.x);
	diffuse = 1.0;
	if(mx < 0.0)
	{
		mx = -mx;
		my = -my;
		diffuse = -1.0;
	}

	vertices = diffuse > 0.0 ? addVs : subVs;

	//
	// Draw edge
	//

	v1.p[0] = p1.x + filterSize.x * mx;
	v1.p[1] = p1.y + filterSize.y * my;
	setLookupTableCoordinates(0, h - 1, tableZ, v1.tex[0]);
	v1.diffuse = color;

	v2.p[0] = p1.x;
	v2.p[1] = p1.y;
	setLookupTableCoordinates(w / 2 - 2, h / 2 + 1, tableZ, v2.tex[0]);
	v2.diffuse = color;

	v3.p[0] = p2.x + filterSize.x * mx;
	v3.p[1] = p2.y + filterSize.y * my;
	copyTexCoord(v3.tex[0], v1.tex[0]);
	v3.diffuse = color;

	v4.p[0] = p2.x;
	v4.p[1] = p2.y;
	copyTexCoord(v4.tex[0], v2.tex[0]);
	v4.diffuse = color;

	int index1 = vertices->addVertex(&v1);
	int index2 = vertices->addVertex(&v2);
	int index3 = vertices->addVertex(&v3);
	int index4 = vertices->addVertex(&v4);

	vertices->addTriangle(index1, index2, index3);
	vertices->addTriangle(index2, index4, index3);
	vertices->endBlock();

	v1.p[0] = p1.x - filterSize.x * mx;
	v1.p[1] = p1.y - filterSize.y * my;
	setLookupTableCoordinates(w - 1, 0, tableZ, v1.tex[0]);

	v2.p[0] = p1.x;
	v2.p[1] = p1.y;
	setLookupTableCoordinates(w / 2 + 1, h / 2 - 2, tableZ, v2.tex[0]);

	v3.p[0] = p2.x - filterSize.x * mx;
	v3.p[1] = p2.y - filterSize.y * my;
	copyTexCoord(v3.tex[0], v1.tex[0]);

	v4.p[0] = p2.x;
	v4.p[1] = p2.y;
	copyTexCoord(v4.tex[0], v2.tex[0]);

	index1 = vertices->addVertex(&v1);
	index2 = vertices->addVertex(&v2);
	index3 = vertices->addVertex(&v3);
	index4 = vertices->addVertex(&v4);

	vertices->addTriangle(index1, index2, index3);
	vertices->addTriangle(index2, index4, index3);
	vertices->endBlock();

	//
	// Draw corners
	//

	drawCorner(p1, tableZ, filterSize.x * mx, filterSize.y * my, mx * my);
	drawCorner(p1, tableZ, -filterSize.x * mx, -filterSize.y * my, mx * my);
	drawCorner(p2, tableZ, filterSize.x * mx, filterSize.y * my, - mx * my);
	drawCorner(p2, tableZ, -filterSize.x * mx, -filterSize.y * my, - mx * my); 
}
